﻿using System;
using System.Collections.Generic;
using System.Disposables;
using System.Linq;
using Collab.Modules.Editing.Interfaces;
using Collab.Services.Remote.Entities;
using Microsoft.VisualStudio.Text;

namespace Collab.Modules.Editing
{
    public class VSXEditBuffer : IEditBuffer
    {
        private readonly CompositeDisposable disposable = new CompositeDisposable();
        private readonly ITextBuffer buffer;
        private readonly object editTag = new object();
        private readonly int revisionDelta = 0;
        private readonly ChangeProcessor changeProcessor = new ChangeProcessor();

        private IEnumerable<Change> previousChanges = Enumerable.Empty<Change>();
        
        public VSXEditBuffer(ITextBuffer buffer, Guid userID, string documentName, DocumentSnapshot document = null)
        {
            this.buffer = buffer;
            this.UserId = userID;
            this.DocumentName = documentName;
            this.disposable.Add(Observable.FromEvent<TextContentChangedEventArgs>(handler => this.buffer.Changed += handler, handler => this.buffer.Changed -= handler)
                .Where(e => e.EventArgs.EditTag != this.editTag)
                .Select(e => new BufferUpdate(this.DocumentName,
                        this.UserId,
                        e.EventArgs.AfterVersion.VersionNumber,
                        e.EventArgs.Changes.Select(change => new Change(change.OldPosition, change.OldEnd, change.NewLength - change.OldLength, change.NewText)).ToList()
                    )
                 )
                 .Subscribe(this.changeProcessor));

            if (document != null)
            {
                ITextEdit edit = buffer.CreateEdit(EditOptions.None, null, this.editTag);
                edit.Replace(0, edit.Snapshot.Length, document.Text);
                edit.Apply();
                this.revisionDelta = document.Revision - this.buffer.CurrentSnapshot.Version.VersionNumber;
            }
                        
            this.disposable.Add(this.changeProcessor
                .Where(update => update.Sender != userID)
                .Subscribe(update =>
                {
                    var edit = this.buffer.CreateEdit(EditOptions.None, null, this.editTag);
                    update.Changes.Run(change => edit.Replace(change.OldStart, change.OldEnd - change.OldStart, change.Text));
                    edit.Apply();
                }));
        }


        #region IEditBuffer Members

        public string DocumentName { get; private set; }
        
        public Guid UserId { get; private set; }

        public DocumentSnapshot CurrentSnapshot
        {
            get { return new DocumentSnapshot() { Text = this.buffer.CurrentSnapshot.GetText(), Revision = this.buffer.CurrentSnapshot.Version.VersionNumber }; }
        }
                
        #endregion

        #region IObserver<BufferUpdate> Members
        
        public void OnNext(BufferUpdate update)
        {
            //Debug.WriteLine("FROM SERVICE  \t  Revision:" + update.Revision + " Sender: " +  update.Sender + " At:" + Collab.Common.CollabDebug.UserName);
            this.changeProcessor.OnNext(new BufferUpdate(update.DocumentName, update.Sender, update.Revision - this.revisionDelta, update.Changes));
        }

        public void OnCompleted()
        {
            this.changeProcessor.OnCompleted();
        }

        public void OnError(Exception error)
        {
            this.changeProcessor.OnError(error);
        }

        #endregion

        #region IObservable<BufferUpdate> Members

        public IDisposable Subscribe(IObserver<BufferUpdate> observer)
        {
            return this.changeProcessor.Select(update => new BufferUpdate(update.DocumentName, update.Sender, update.Revision + this.revisionDelta, update.Changes))
                .Subscribe(observer);
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this.changeProcessor.Dispose();
            this.disposable.Dispose();
        }

        #endregion

        #region IEditBuffer Members


        public int Revision
        {
            get { return this.changeProcessor.Revision; }
        }

        #endregion
    }
}
